package com.silence.gmall.util;

import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.util.StringUtils;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Objects;
import java.util.regex.Pattern;

/**
 * IP地址获取工具类
 * 提供从HTTP请求中获取客户端真实IP地址的功能
 * 支持各种代理和负载均衡场景
 *
 * @author Silence
 * @version 1.0
 * @since 1.0
 */
@Slf4j
public class IpUtil {

    /**
     * 未知IP标识符
     */
    private static final String UNKNOWN = "unknown";

    /**
     * 本地回环地址
     */
    private static final String LOCALHOST_IPV4 = "127.0.0.1";
    private static final String LOCALHOST_IPV6 = "0:0:0:0:0:0:0:1";

    /**
     * IP地址正则表达式
     */
    private static final Pattern IP_PATTERN = Pattern.compile(
            "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$"
    );

    /**
     * 常用的IP头部字段名称（按优先级排序）
     */
    private static final String[] IP_HEADER_NAMES = {
            "X-Forwarded-For",
            "x-forwarded-for",
            "Proxy-Client-IP",
            "WL-Proxy-Client-IP",
            "HTTP_CLIENT_IP",
            "HTTP_X_FORWARDED_FOR",
            "X-Real-IP"
    };

    /**
     * 私有构造器，防止实例化工具类
     */
    private IpUtil() {
        throw new UnsupportedOperationException("工具类不支持实例化");
    }

    /**
     * 从HttpServletRequest中获取客户端真实IP地址
     *
     * @param request HTTP请求对象，不能为null
     * @return 客户端IP地址，获取失败时返回空字符串
     * @throws IllegalArgumentException 如果request为null
     */
    public static String getIpAddress(HttpServletRequest request) {
        Objects.requireNonNull(request, "HttpServletRequest不能为null");

        try {
            // 1. 尝试从各种代理头部获取IP
            for (String headerName : IP_HEADER_NAMES) {
                String ip = request.getHeader(headerName);
                if (isValidIpBasic(ip)) {
                    return extractFirstIp(ip);
                }
            }

            // 2. 从远程地址获取IP
            String remoteAddr = request.getRemoteAddr();
            if (isValidIpBasic(remoteAddr)) {
                // 如果是本地地址，尝试获取本机真实IP
                if (isLocalhost(remoteAddr)) {
                    String localIp = getLocalHostIp();
                    return StringUtils.hasText(localIp) ? localIp : remoteAddr;
                }
                return remoteAddr;
            }

            log.warn("无法获取有效的IP地址，请求信息: {}", getRequestInfo(request));
            return "";

        } catch (Exception e) {
            log.error("获取IP地址时发生异常", e);
            return "";
        }
    }

    /**
     * 从ServerHttpRequest中获取客户端真实IP地址（适用于Spring WebFlux）
     *
     * @param request 响应式HTTP请求对象，不能为null
     * @return 客户端IP地址，获取失败时返回空字符串
     * @throws IllegalArgumentException 如果request为null
     */
    public static String getGatewayIpAddress(ServerHttpRequest request) {
        Objects.requireNonNull(request, "ServerHttpRequest不能为null");

        try {
            HttpHeaders headers = request.getHeaders();

            // 尝试从各种代理头部获取IP
            for (String headerName : IP_HEADER_NAMES) {
                String ip = headers.getFirst(headerName);
                if (isValidIpBasic(ip)) {
                    return extractFirstIp(ip);
                }
            }

            // 从远程地址获取IP
            String remoteAddr = null;
            if (request.getRemoteAddress() != null && request.getRemoteAddress().getAddress() != null) {
                remoteAddr = request.getRemoteAddress().getAddress().getHostAddress();
            }

            if (isValidIpBasic(remoteAddr)) {
                return remoteAddr;
            }

            log.warn("无法获取有效的IP地址，远程地址: {}", remoteAddr);
            return "";

        } catch (Exception e) {
            log.error("获取网关IP地址时发生异常", e);
            return "";
        }
    }

    /**
     * 验证IP地址是否有效（公共方法，包含格式验证）
     *
     * @param ip IP地址字符串
     * @return true：有效，false：无效
     */
    public static boolean isValidIp(String ip) {
        return StringUtils.hasText(ip) 
                && !UNKNOWN.equalsIgnoreCase(ip.trim())
                && IP_PATTERN.matcher(ip.trim()).matches();
    }

    /**
     * 判断是否为本地地址
     *
     * @param ip IP地址
     * @return true：是本地地址，false：不是本地地址
     */
    public static boolean isLocalhost(String ip) {
        return LOCALHOST_IPV4.equals(ip) || LOCALHOST_IPV6.equals(ip) || "localhost".equalsIgnoreCase(ip);
    }

    /**
     * 获取本机IP地址
     *
     * @return 本机IP地址，获取失败时返回null
     */
    public static String getLocalHostIp() {
        try {
            InetAddress inetAddress = InetAddress.getLocalHost();
            return inetAddress.getHostAddress();
        } catch (UnknownHostException e) {
            log.warn("获取本机IP地址失败", e);
            return null;
        }
    }

    /**
     * 判断IP地址是否有效（私有方法，仅检查空值和unknown）
     *
     * @param ip IP地址字符串
     * @return true：有效，false：无效
     */
    private static boolean isValidIpBasic(String ip) {
        return StringUtils.hasText(ip) && !UNKNOWN.equalsIgnoreCase(ip.trim());
    }

    /**
     * 从IP字符串中提取第一个有效IP地址
     * 处理多个IP地址用逗号分隔的情况
     *
     * @param ip 原始IP字符串
     * @return 第一个有效的IP地址
     */
    private static String extractFirstIp(String ip) {
        if (!StringUtils.hasText(ip)) {
            return ip;
        }

        // 处理多个IP地址的情况，取第一个
        if (ip.contains(",")) {
            String[] ips = ip.split(",");
            for (String singleIp : ips) {
                String trimmedIp = singleIp.trim();
                if (isValidIpBasic(trimmedIp)) {
                    return trimmedIp;
                }
            }
        }

        return ip.trim();
    }

    /**
     * 获取请求的基本信息用于日志记录
     *
     * @param request HTTP请求对象
     * @return 请求信息字符串
     */
    private static String getRequestInfo(HttpServletRequest request) {
        return String.format("URI: %s, RemoteAddr: %s, UserAgent: %s",
                request.getRequestURI(),
                request.getRemoteAddr(),
                request.getHeader("User-Agent"));
    }
}